<?php

    /**
     * @package      Barcode
     * @subpackage   Adapter
     *
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    class Barcode_Adapter_Code128 extends Barcode_Adapter_Abstract
    {

    ////////////////////////////////////////////////////////////////////////////
    //                            Attributes                                  //
    ////////////////////////////////////////////////////////////////////////////

        /**
         * Start code
         * 
         * @var     string
         */
        private $_startCode = '211214';

        /**
         * Stop code
         *
         * @var     string
         */
        private $_stopCode = '2331112';

    ////////////////////////////////////////////////////////////////////////////
    //                            Methods                                     //
    ////////////////////////////////////////////////////////////////////////////

        /**
         * Constructor
         */
        public function __construct()
        {
            /*
             * Defines path
             */
            $this->_path = Core_IO_Path::combine(
                                                    PROJECT_TEMP_PATH,
                                                    'barcodes',
                                                    'code128'
                                                );

            if (!Core_IO_Directory::exists($this->_path)) {
                Core_IO_Directory::createDirectory($this->_path);
            }

            /*
             * Coding map
             */
            $this->_codingMap[0]  = "212222";  // " "
            $this->_codingMap[1]  = "222122";  // "!"
            $this->_codingMap[2]  = "222221";  // "{QUOTE}"
            $this->_codingMap[3]  = "121223";  // "#"
            $this->_codingMap[4]  = "121322";  // "$"
            $this->_codingMap[5]  = "131222";  // "%"
            $this->_codingMap[6]  = "122213";  // "&"
            $this->_codingMap[7]  = "122312";  // "'"
            $this->_codingMap[8]  = "132212";  // "("
            $this->_codingMap[9]  = "221213";  // ")"
            $this->_codingMap[10] = "221312"; // "*"
            $this->_codingMap[11] = "231212"; // "+"
            $this->_codingMap[12] = "112232"; // ","
            $this->_codingMap[13] = "122132"; // "-"
            $this->_codingMap[14] = "122231"; // "."
            $this->_codingMap[15] = "113222"; // "/"
            $this->_codingMap[16] = "123122"; // "0"
            $this->_codingMap[17] = "123221"; // "1"
            $this->_codingMap[18] = "223211"; // "2"
            $this->_codingMap[19] = "221132"; // "3"
            $this->_codingMap[20] = "221231"; // "4"
            $this->_codingMap[21] = "213212"; // "5"
            $this->_codingMap[22] = "223112"; // "6"
            $this->_codingMap[23] = "312131"; // "7"
            $this->_codingMap[24] = "311222"; // "8"
            $this->_codingMap[25] = "321122"; // "9"
            $this->_codingMap[26] = "321221"; // ":"
            $this->_codingMap[27] = "312212"; // ";"
            $this->_codingMap[28] = "322112"; // "<"
            $this->_codingMap[29] = "322211"; // "="
            $this->_codingMap[30] = "212123"; // ">"
            $this->_codingMap[31] = "212321"; // "?"
            $this->_codingMap[32] = "232121"; // "@"
            $this->_codingMap[33] = "111323"; // "A"
            $this->_codingMap[34] = "131123"; // "B"
            $this->_codingMap[35] = "131321"; // "C"
            $this->_codingMap[36] = "112313"; // "D"
            $this->_codingMap[37] = "132113"; // "E"
            $this->_codingMap[38] = "132311"; // "F"
            $this->_codingMap[39] = "211313"; // "G"
            $this->_codingMap[40] = "231113"; // "H"
            $this->_codingMap[41] = "231311"; // "I"
            $this->_codingMap[42] = "112133"; // "J"
            $this->_codingMap[43] = "112331"; // "K"
            $this->_codingMap[44] = "132131"; // "L"
            $this->_codingMap[45] = "113123"; // "M"
            $this->_codingMap[46] = "113321"; // "N"
            $this->_codingMap[47] = "133121"; // "O"
            $this->_codingMap[48] = "313121"; // "P"
            $this->_codingMap[49] = "211331"; // "Q"
            $this->_codingMap[50] = "231131"; // "R"
            $this->_codingMap[51] = "213113"; // "S"
            $this->_codingMap[52] = "213311"; // "T"
            $this->_codingMap[53] = "213131"; // "U"
            $this->_codingMap[54] = "311123"; // "V"
            $this->_codingMap[55] = "311321"; // "W"
            $this->_codingMap[56] = "331121"; // "X"
            $this->_codingMap[57] = "312113"; // "Y"
            $this->_codingMap[58] = "312311"; // "Z"
            $this->_codingMap[59] = "332111"; // "["
            $this->_codingMap[60] = "314111"; // "\"
            $this->_codingMap[61] = "221411"; // "]"
            $this->_codingMap[62] = "431111"; // "^"
            $this->_codingMap[63] = "111224"; // "_"
            $this->_codingMap[64] = "111422"; // "`"
            $this->_codingMap[65] = "121124"; // "a"
            $this->_codingMap[66] = "121421"; // "b"
            $this->_codingMap[67] = "141122"; // "c"
            $this->_codingMap[68] = "141221"; // "d"
            $this->_codingMap[69] = "112214"; // "e"
            $this->_codingMap[70] = "112412"; // "f"
            $this->_codingMap[71] = "122114"; // "g"
            $this->_codingMap[72] = "122411"; // "h"
            $this->_codingMap[73] = "142112"; // "i"
            $this->_codingMap[74] = "142211"; // "j"
            $this->_codingMap[75] = "241211"; // "k"
            $this->_codingMap[76] = "221114"; // "l"
            $this->_codingMap[77] = "413111"; // "m"
            $this->_codingMap[78] = "241112"; // "n"
            $this->_codingMap[79] = "134111"; // "o"
            $this->_codingMap[80] = "111242"; // "p"
            $this->_codingMap[81] = "121142"; // "q"
            $this->_codingMap[82] = "121241"; // "r"
            $this->_codingMap[83] = "114212"; // "s"
            $this->_codingMap[84] = "124112"; // "t"
            $this->_codingMap[85] = "124211"; // "u"
            $this->_codingMap[86] = "411212"; // "v"
            $this->_codingMap[87] = "421112"; // "w"
            $this->_codingMap[88] = "421211"; // "x"
            $this->_codingMap[89] = "212141"; // "y"
            $this->_codingMap[90] = "214121"; // "z"
            $this->_codingMap[91] = "412121"; // "{"
            $this->_codingMap[92] = "111143"; // "|"
            $this->_codingMap[93] = "111341"; // "}"
            $this->_codingMap[94] = "131141"; // "~"
            $this->_codingMap[95] = "114113"; // 95
            $this->_codingMap[96] = "114311"; // 96
            $this->_codingMap[97] = "411113"; // 97
            $this->_codingMap[98] = "411311"; // 98
            $this->_codingMap[99] = "113141"; // 99
            $this->_codingMap[100] = "114131"; // 100
            $this->_codingMap[101] = "311141"; // 101
            $this->_codingMap[102] = "411131"; // 102
        }

        /**
         * Creates the barcode image.
         *
         * @param       int                 $value
         * @throws      Core_Exception
         * @return      string              Image path
         */
        public function process($value)
        {
            /*
             * Check value validity
             */
            foreach (str_split($value) as $c) {
                if (ord($c) - 32 < 0 || ord($c) - 32 > 102) {
                    throw new Core_Exception('Fatal error : Invalid characters for code128 barcode');
                }
            }

            $cacheFile = Core_IO_Path::combine($this->_path, $value . ".png");

            if (Core_IO_File::exists($cacheFile)) {
                // If barcode is still in cache return its path
                return $cacheFile;
            }

            /*
             * We start with the Code128 Start Code character.  We initialize checksum to 104, rather than calculate it.
             * 
             * We then add the startcode to $allbars, the main string containing the bar sizes for the entire code.
             */
            $checksum   = 104;
            $barcode    = $this->_startCode;

            /*
             * Next, we read the $text string that was passed to the method and for each character,
             * we determine the bar pattern and add it to the end of the $allbars string.
             *
             * In addition, we continually add the character's value to the checksum.
             */
            for ($i = 0; $i < strlen($value); ++$i) {
                $char       = $value[$i];
                $val        = ord($char) - 32; // Get ASCII code
                $checksum  += ($val * ($i + 1));

                $barcode .= $this->_codingMap[ord($char) - 32];
            }

            /*
             * Then, Take the Mod 103 of the total to get the index of the Code128 Check Character.
             * We get its bar pattern and add it to $allbars in the next section.
             */
            $bars = $this->_codingMap[$checksum % 103];

            /*
             * Finally, we get the Stop Code pattern and put the remaining pieces together.  We are left with the
             * string $allbars containing all of the bar widths and can now think about writing it to the image.
             */
            $barcode .= $bars . $this->_stopCode;

            /*
             * Next, we will calculate the width of the resulting bar code and size the image accordingly.
             * 10 Pixel "Quiet Zone" in front, and 10 Pixel "Quiet Zone" at the end.
             */
            $barcodewidth = 20;

            /*
             * We will read each of the characters (1,2,3,or 4) in the $allbars string and add its width to the running
             * total $barcodewidth.  The height of the barcode is calculated by taking the bar height plus the font height.
             */
            for ($i = 0; $i < strlen($barcode); ++$i) {
                $nval           = $barcode[$i];
                $barcodewidth  += ($nval * $this->_barWidth);
            }

            $fontHeight         = ImageFontHeight($this->_fontSize);
            $fontWidth          = ImageFontWidth($this->_fontSize);
            $barcodelongheight  = (int) ($fontHeight / 2) + $this->_barcodeHeight;

            /*
             * Create GD image object
             */
            $img = ImageCreate($barcodewidth, $barcodelongheight + $fontHeight + 1);

            /*
             * Allocate black and white colors to the image
             */
            $black = ImageColorAllocate($img, 0, 0, 0);
            $white = imageColorAllocate($img, 255, 255, 255);

            /*
             * Fill background with white color
             */
            ImageFill($img, 0, 0, $white);

            /*
             * First, print the image, centered across the bottom.
             */
            ImageString($img,
                        $this->_fontSize,
                        $barcodewidth / 2 - strlen($value) / 2 * $fontWidth,
                        $this->_barcodeHeight + $fontHeight / 2,
                        $value,
                        $black);

            /*
             * We set $xpos to 10 so we start bar printing after position 10 to simulate the 10 pixel "Quiet Zone"
             */
            $xpos = 10;

            /*
             * We will now process each of the characters in the $allbars array.
             * The number in each position is read and then alternating black bars and spaces are drawn with the corresponding width.
             */
            $bar = 1;
            for ($i = 0; $i < strlen($barcode); ++$i) {
                $nval   = $barcode[$i];
                $width  = $nval * $this->_barWidth;

                if ($bar == 1) {
                    ImageFilledRectangle($img, $xpos, 0, $xpos + $width-1, $barcodelongheight, $black);
                    $xpos += $width;
                    $bar = 0;
                } else {
                    $xpos += $width;
                    $bar = 1;
                }
            }

            ImagePNG($img, $cacheFile);
            ImageDestroy($img);

            return $cacheFile;
        }
    }